﻿using System;
using System.Collections.Generic;
using System.Linq;
using SharpStockLib.DataAccess.Providers;
using SharpStockLib.Data;
using SharpBackTestLib.Strategies;
using SharpStockLib.Fin;
using SharpBackTestLib.Orders;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Collections.Concurrent;

namespace SharpBackTestConsole
{
    /// <summary>
    /// This is some kind of a test program, not an actual software to put in production. The code is messy and there is no plan to make it cleaner in a near future.
    /// This is used mostly to test the complete scenario and measure performance of various methods.
    /// 
    /// USAGE : SharpBackTestConsole.exe &lt;path to quotes database&gt;
    /// </summary>
    class Program
    {
        public static object lockObject = new object();
        public static Dictionary<DateTime, SignalsOfTheDay> signals = new Dictionary<DateTime,SignalsOfTheDay>();

        static void Main(string[] args)
        {
            Console.BufferHeight = 3500;
            Stopwatch sw = Stopwatch.StartNew();

            SQLiteProvider dataStream = new SQLiteProvider(args[0]);

            List<string> symbols = dataStream.ListStocks();
            List<IInstrument> stocks = symbols.Select(s => new Stock(s)).ToList<IInstrument>();

            // for futures
            //List<string> symbols = dataStream.ListFutures();
            //List<IInstrument> stocks = symbols.Select(s => new Future(s)).ToList<IInstrument>();

            // Various methods : basic Parallel.For, Pipeline pattern, sequential.
            RunParallel(stocks, dataStream, sw);
            //RunPipeline(stocks, dataStream, sw);
            //RunSequential(stocks, dataStream, sw);
            
            dataStream.Dispose();
            Console.WriteLine(signals.Keys.Count + " dates with signals");
            List<DateTime> orderedDates = signals.Keys.OrderBy(date => date).ToList();
            /*foreach (DateTime date in orderedDates)
            {
                Console.WriteLine(date.ToString("dd/MM/yyyy") + " : " + signals[date].entrySignals.Count + " entries, " + signals[date].exitSignals.Count + " exits");
            }*/
            Console.ReadLine();
        }

        /// <summary>
        /// Sequential run using a good old for loop
        /// </summary>
        /// <param name="stocks"></param>
        /// <param name="dataStream"></param>
        /// <param name="sw"></param>
        public static void RunSequential(List<Stock> stocks, SQLiteProvider dataStream, Stopwatch sw)
        {
            for (int i = 0; i < stocks.Count; i++)
            {
                IEnumerable<IData> quotes = dataStream.GetHistoricalData(stocks[i].Symbol, null, null);
                if (quotes != null)
                {
                    SMACrossoverStrategy strat = new SMACrossoverStrategy(stocks[i], 50, 200);
                    strat.EntrySignal += OnEntrySignal;
                    strat.ExitSignal += OnExitSignal;

                    foreach (IData data in quotes)
                    {
                        strat.OnNewData(data);
                    }

                    Console.WriteLine(stocks[i].Symbol + " complete in " + sw.Elapsed);
                }
            }
        }

        /// <summary>
        /// Loads data in parallel and uses Parallel.ForEach to handle them
        /// </summary>
        /// <param name="stocks"></param>
        /// <param name="dataStream"></param>
        /// <param name="sw"></param>
        public static void RunParallel(List<IInstrument> stocks, SQLiteProvider dataStream, Stopwatch sw)
        {
            Dictionary<string, IInstrument> stockFromName = new Dictionary<string, IInstrument>(stocks.Count);
            foreach (IInstrument stock in stocks)
            {
                //strat.AddInstrument(stock);
                stockFromName[stock.Symbol] = stock;
            }
            Dictionary<string, List<IData>> stream = dataStream.FullLoadParallel(stocks.Select(s => s.Symbol).ToList());
            Console.WriteLine("Data read in " + sw.Elapsed);

            // Attempts using an OrderablePartitioner on the stream's keys. Turned out not to be very useful.
            /*OrderablePartitioner<string> part = Partitioner.Create(stream.Keys);

            Parallel.ForEach(part, (s, loopState, l) =>
                {
                    SMACrossoverStrategy strat = new SMACrossoverStrategy(50, 200);
                    strat.EntrySignal += OnEntrySignal;
                    strat.ExitSignal += OnExitSignal;

                    foreach (IData data in stream[s])
                    {
                        strat.OnNewData(stockFromName[s], data);
                    }

                    Console.WriteLine(s + " complete in " + sw.Elapsed);
                });*/

            Parallel.ForEach(stream.Keys, symbol =>
            {
                SMACrossoverStrategy strat = new SMACrossoverStrategy(stockFromName[symbol], 50, 200);
                strat.EntrySignal += OnEntrySignal;
                strat.ExitSignal += OnExitSignal;
                var orderedList = stream[symbol].OrderBy(data => data.Date);

                foreach (IData data in orderedList)
                {
                    strat.OnNewData(data);
                }

                //Console.WriteLine(symbol + " complete in " + sw.Elapsed);
            });

            Console.WriteLine("Complete in " + sw.Elapsed);
            stream.Clear();
        }

        /// <summary>
        /// Uses the Pipeline pattern (http://msdn.microsoft.com/en-us/library/ff963548.aspx) to load and manage signals.
        /// Nice but since the main bottleneck is reading data from the DB, it was not stunning performance-wise. But an interesting pattern to know.
        /// </summary>
        /// <param name="stocks"></param>
        /// <param name="dataStream"></param>
        /// <param name="sw"></param>
        public static void RunPipeline(List<Stock> stocks, SQLiteProvider dataStream, Stopwatch sw)
        {
            Dictionary<string, Stock> stockFromName = new Dictionary<string, Stock>(stocks.Count);
            foreach (Stock stock in stocks)
            {
                //strat.AddInstrument(stock);
                stockFromName[stock.Symbol] = stock;
            }

            BlockingCollection<Dictionary<string, IData>> data = new BlockingCollection<Dictionary<string, IData>>();
            Dictionary<string, SMACrossoverStrategy> strats = new Dictionary<string, SMACrossoverStrategy>();

            var factory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);

            var readTask = factory.StartNew(() =>
                {
                    //dataStream.FullLoadPipeline(data);
                });
            var computeTask = factory.StartNew(() =>
                {
                    foreach (Dictionary<string, IData> dataBlock in data.GetConsumingEnumerable())
                    {
                        foreach (string symbol in dataBlock.Keys)
                        {
                            if(!strats.ContainsKey(symbol))
                                strats[symbol] = new SMACrossoverStrategy(stockFromName[symbol], 50, 200);

                            strats[symbol].OnNewData(dataBlock[symbol]);
                        }
                    }
                });

            Task.WaitAll(readTask, computeTask);
            Console.WriteLine("Done in " + sw.Elapsed);
        }

        public static void OnEntrySignal(object sender, EntrySignalEventArgs e)
        {
            EntrySignal signal = e.Signal;
            IData data = signal.Trigger;
            lock (lockObject)
            {
                if (!signals.ContainsKey(data.Date))
                    signals[data.Date] = new SignalsOfTheDay();

                signals[data.Date].entrySignals.Add(signal);
            }
            //Console.WriteLine("ENTRY SIGNAL for " + signal.Instrument.Symbol + ": on " + data.Date + ", " + signal.Type.ToString() + " signal on close (" + data.Close + ")");
        }

        public static void OnExitSignal(object sender, ExitSignalEventArgs e)
        {
            ExitSignal signal = e.Signal;
            IData data = signal.Trigger;
            lock (lockObject)
            {
                if (!signals.ContainsKey(data.Date))
                    signals[data.Date] = new SignalsOfTheDay();

                signals[data.Date].exitSignals.Add(signal);
            }
            //Console.WriteLine("** EXIT SIGNAL for " + signal.Instrument.Symbol + ": on " + data.Date + ", " + signal.Type.ToString() + " signal on close (" + data.Close + ")");
        }
    }

    public class SignalsOfTheDay
    {
        public List<EntrySignal> entrySignals;
        public List<ExitSignal> exitSignals;

        public SignalsOfTheDay()
        {
            entrySignals = new List<EntrySignal>();
            exitSignals = new List<ExitSignal>();
        }
    }
}
